package smba_driver

import (
	"errors"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/driver"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/chicken-team-outside/chicken_transmission/utils"
	"github.com/hirochachacha/go-smb2"
	"net"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

func init() {
}

func formatPath(path string) string {
	return strings.TrimLeft(path, "/")
}

func Create(auth SmbAuthInfo) (driver.LocalDriver, error) {
	if auth.Port == 0 {
		auth.Port = 445
	}
	auth.Address = fmt.Sprintf("%s:%d", auth.Address, auth.Port)
	d := &SmbaDriver{SmbAuthInfo: auth}
	err := d.initFS()
	if err != nil {
		return nil, err
	}
	return d, nil
}

type SmbaDriver struct {
	SmbAuthInfo
	lastConnTime int64
	fs           *smb2.Share
	uniqueLock   sync.Mutex
}

func (s *SmbaDriver) checkParentOrCreate(path string) error {
	parent := formatPath(utils.GetDir(path))
	s.uniqueLock.Lock()
	defer s.uniqueLock.Unlock()
	_, err := s.Stat(parent)
	if errors.Is(err, os.ErrNotExist) {
		err = s.MkdirAll(parent, os.ModeDir)
		if errors.Is(err, os.ErrExist) {
			err = nil
		}
	}
	return err
}

func (s *SmbaDriver) Stat(name string) (os.FileInfo, error) {
	if err := s.checkConn(); err != nil {
		return nil, err
	}
	info, err := s.fs.Stat(formatPath(name))
	if err != nil {
		s.cleanLastConnTime()
		return nil, err
	}
	s.updateLastConnTime()
	return info, nil
}

func (s *SmbaDriver) OpenFile(path string, flag int, mode os.FileMode) (f driver.LocalDriverFile, err error) {
	if err = s.checkConn(); err != nil {
		return
	}
	path = formatPath(path)
	err = s.checkParentOrCreate(path)
	if err == nil {
		f, err = s.fs.OpenFile(path, flag, mode)
	}
	if err != nil {
		s.cleanLastConnTime()
		return
	}
	s.updateLastConnTime()
	return
}

func (s *SmbaDriver) MkdirAll(path string, perm os.FileMode) error {
	if err := s.checkConn(); err != nil {
		return err
	}
	err := s.fs.MkdirAll(path, perm)
	if err != nil {
		s.cleanLastConnTime()
		return err
	}
	return nil
}

func (s *SmbaDriver) updateLastConnTime() {
	atomic.StoreInt64(&s.lastConnTime, time.Now().Unix())
}

func (s *SmbaDriver) cleanLastConnTime() {
	atomic.StoreInt64(&s.lastConnTime, 0)
}

func (s *SmbaDriver) getLastConnTime() time.Time {
	return time.Unix(atomic.LoadInt64(&s.lastConnTime), 0)
}

func (s *SmbaDriver) initFS() error {
	conn, err := net.Dial("tcp", s.Address)
	if err != nil {
		return err
	}
	dialer := &smb2.Dialer{
		Initiator: &smb2.NTLMInitiator{
			User:     s.Username,
			Password: s.Password,
		},
	}
	s_, err := dialer.Dial(conn)
	if err != nil {
		return err
	}
	s.fs, err = s_.Mount(s.ShareName)
	if err != nil {
		return err
	}
	s.updateLastConnTime()
	return err
}

func (s *SmbaDriver) checkConn() error {
	if time.Since(s.getLastConnTime()) < 5*time.Minute {
		return nil
	}
	if s.fs != nil {
		_ = s.fs.Umount()
	}
	return s.initFS()
}

func (s *SmbaDriver) Remove(targetPath string) error {
	if err := s.checkConn(); err != nil {
		return err
	}
	var err error
	targetPath = strings.TrimLeft(targetPath, "/")
	stat, err := s.fs.Stat(targetPath)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		err = s.fs.RemoveAll(targetPath)
	} else {
		err = s.fs.Remove(targetPath)
	}
	if err != nil {
		s.cleanLastConnTime()
		return err
	}
	s.updateLastConnTime()
	return nil
}

// Exists determine whether the file exists
func (s *SmbaDriver) Exists(name string) (bool, error) {
	name = strings.TrimLeft(name, "/")
	if _, err := s.fs.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

func (s *SmbaDriver) ListFiles(dirPath string) ([]os.FileInfo, error) {
	if err := s.checkConn(); err != nil {
		return nil, err
	}
	rawFiles, err := s.fs.ReadDir(strings.TrimLeft(dirPath, "/"))
	if err != nil {
		s.cleanLastConnTime()
		return nil, err
	}
	s.updateLastConnTime()
	return rawFiles, nil
}

func (s *SmbaDriver) Rename(oldPath string, newPath string) error {
	if err := s.checkConn(); err != nil {
		return err
	}
	err := s.fs.Rename(oldPath, newPath)
	if err != nil {
		s.cleanLastConnTime()
		return err
	}
	s.updateLastConnTime()
	return nil
}
func (s *SmbaDriver) Truncate(path string, size int64) error {
	if err := s.checkConn(); err != nil {
		return err
	}
	err := s.fs.Truncate(path, size)
	if err != nil {
		s.cleanLastConnTime()
		return err
	}
	s.updateLastConnTime()
	return nil
}
func (s *SmbaDriver) GetVendor() model.Vendor {
	return model.Vendor_SMB2
}

func (s *SmbaDriver) Close() error {
	if s.fs != nil {
		_ = s.fs.Umount()
	}
	return nil
}
